/* SPDX-License-Identifier: BSD-3-Clause */

/*
 * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd.
 */

#include "Soc.h"
#include <Library/DebugLib.h>
#include <Library/IoLib.h>
#include <Library/PcdLib.h>
#include <Library/TimerLib.h>
#include <Library/SpiMem.h>
#include <Library/Fspi.h>

#define HAL_IS_ALIGNED(x, a)  (((x) & (a - 1)) == 0)
/********************* Private MACRO Definition ******************************/
// #define FSPI_DEBUG
#ifdef FSPI_DEBUG
#define FSPI_DBG(...)
#else
#define FSPI_DBG(...)
#endif

/* FSPI_CTRL */
#define FSPI_CTRL_SHIFTPHASE_NEGEDGE  1

/* FSPI_RCVR */
#define FSPI_RCVR_RCVR_RESET  (1 << FSPI_RCVR_RCVR_SHIFT)/* Recover The FSPI State Machine */

/* FSPI_ISR */
#define FSPI_ISR_DMAS_ACTIVE    (1 << FSPI_ISR_DMAS_SHIFT)   /* DMA Finish Interrupt Active */
#define FSPI_ISR_NSPIS_ACTIVE   (1 << FSPI_ISR_NSPIS_SHIFT)  /* SPI Error Interrupt Active */
#define FSPI_ISR_AHBS_ACTIVE    (1 << FSPI_ISR_AHBS_SHIFT)   /* AHB Error Interrupt Active */
#define FSPI_ISR_TRANSS_ACTIVE  (1 << FSPI_ISR_TRANSS_SHIFT) /* Transfer finish Interrupt Active */
#define FSPI_ISR_TXES_ACTIVE    (1 << FSPI_ISR_TXES_SHIFT)   /* Transmit FIFO Empty Interrupt Active */
#define FSPI_ISR_TXOS_ACTIVE    (1 << FSPI_ISR_TXOS_SHIFT)   /* Transmit FIFO Overflow Interrupt Active */
#define FSPI_ISR_RXUS_ACTIVE    (1 << FSPI_ISR_RXUS_SHIFT)   /* Receive FIFO Underflow Interrupt Active */
#define FSPI_ISR_RXFS_ACTIVE    (1 << FSPI_ISR_RXFS_SHIFT)   /* Receive FIFO Full Interrupt Active */

/* FSPI_FSR */
#define FSPI_FSR_RXFS_EMPTY  (1 << FSPI_FSR_RXFS_SHIFT) /* Receive FIFO Full */
#define FSPI_FSR_RXES_EMPTY  (1 << FSPI_FSR_RXES_SHIFT) /* Receive FIFO Empty */
#define FSPI_FSR_TXFS_FULL   (1 << FSPI_FSR_TXFS_SHIFT) /* Transmit FIFO Full */
#define FSPI_FSR_TXES_EMPTY  (1 << FSPI_FSR_TXES_SHIFT) /* Transmit FIFO Empty */

/* FSPI_SR */
#define FSPI_SR_SR_BUSY  (1 << FSPI_SR_SR_SHIFT)/* When busy, do not set the control register. */

/* FSPI_DMATR */
#define FSPI_DMATR_DMATR_START  (1 << FSPI_DMATR_DMATR_SHIFT)/* Write 1 to start the dma transfer. */

/* FSPI_RISR */
#define FSPI_RISR_TRANSS_ACTIVE  (1 << FSPI_RISR_TRANSS_SHIFT)

/* FSPI attributes */
#define FSPI_VER_VER_1  1
#define FSPI_VER_VER_3  3
#define FSPI_VER_VER_5  5

#define FSPI_NOR_FLASH_PAGE_SIZE  0x100

#define FSPI_MAX_IOSIZE_VER3  (1024U * 8)
#define FSPI_MAX_IOSIZE_VER4  (0xFFFFFFFFU)

/********************* Private Structure Definition **************************/

typedef union {
  UINT32    d32;
  struct {
    unsigned    txempty       : 1; /* tx fifo empty */
    unsigned    txfull        : 1; /* tx fifo full */
    unsigned    rxempty       : 1; /* rx fifo empty */
    unsigned    rxfull        : 1; /* tx fifo empty interrupt mask */
    unsigned    reserved7_4   : 4;
    unsigned    txlevel       : 5; /* tx fifo: 0: full; 1: left 1 entry; ... */
    unsigned    reserved15_13 : 3;
    unsigned    rxlevel       : 5; /* rx fifo: 0: full; 1: left 1 entry; ... */
    unsigned    reserved31_21 : 11;
  } b;
} FSPIFSR_DATA;

/** FSPI_XMMC bit union */
typedef union {
  UINT32    d32;
  struct {
    unsigned    reserverd1      : 5;
    unsigned    devHwEn         : 1; /* device Hwrite Enable */
    unsigned    prefetch        : 1; /* prefetch enable */
    unsigned    uincrPrefetchEn : 1; /* undefine INCR Burst Prefetch Enable */
    unsigned    uincrLen        : 4; /* undefine INCR length */
    unsigned    devWrapEn       : 1; /* device Wrap Enable */
    unsigned    devIncrEn       : 1; /* device INCR2/4/8/16 Enable */
    unsigned    devUdfincrEn    : 1; /* device Undefine INCR Enable */
    unsigned    reserved2       : 17;
  } b;
} FSPIXMMCCTRL_DATA;
/********************* Private Variable Definition ***************************/

/********************* Private Function Definition ***************************/
static void
FSPI_Reset (
  struct HAL_FSPI_HOST  *host
  )
{
  INT32  timeout = 10000;

  host->instance->RCVR = FSPI_RCVR_RCVR_RESET;
  while ((host->instance->RCVR == FSPI_RCVR_RCVR_RESET) && (timeout > 0)) {
    HAL_CPUDelayUs (1);
    timeout--;
  }

  host->instance->ICLR = 0xFFFFFFFF;
}

static void
FSPI_ContModeInit (
  struct HAL_FSPI_HOST  *host
  )
{
  /* Setup when FSPI->AX = 0xa5, Cancel when FSPI->AX = 0x0 */
  WRITE_REG (
    host->instance->EXT_AX,
    0xa5 << FSPI_EXT_AX_AX_SETUP_PAT_SHIFT |
    0x5a << FSPI_EXT_AX_AX_CANCEL_PAT_SHIFT
    );
  /* Skip Continuous mode in default */
  switch (host->cs) {
    case 0:
      WRITE_REG (host->instance->AX0, 0 << FSPI_AX0_AX_SHIFT);
      break;
    case 1:
      WRITE_REG (host->instance->AX1, 0 << FSPI_AX1_AX_SHIFT);
      break;
    default:
      break;
  }
}

static void
FSPI_ContModeDeInit (
  struct HAL_FSPI_HOST  *host
  )
{
  /* FSPI avoid setup pattern match with FSPI->AX */
  WRITE_REG (
    host->instance->EXT_AX,
    0x77 << FSPI_EXT_AX_AX_SETUP_PAT_SHIFT |
    0x88 << FSPI_EXT_AX_AX_CANCEL_PAT_SHIFT
    );
}

/**
 * @brief  Configuration register with flash operation protocol.
 * @param  host: FSPI host.
 * @param  op: flash operation protocol.
 * @return RETURN_STATUS.
 * @attention Set host->cs to select chip.
 */
RETURN_STATUS
HAL_FSPI_XferStart (
  struct HAL_FSPI_HOST   *host,
  struct HAL_SPI_MEM_OP  *op
  )
{
  struct FSPI_REG  *pReg = host->instance;
  FSPICMD_DATA     FSPICmd;
  FSPICTRL_DATA    FSPICtrl;

  FSPICmd.d32  = 0;
  FSPICtrl.d32 = 0;

  /* set CMD */
  FSPICmd.b.cmd = op->cmd.opcode;
  if (op->cmd.buswidth == 4) {
    FSPICtrl.b.cmdlines  = FSPI_LINES_X4;
    FSPICtrl.b.datalines = FSPI_LINES_X4;     /* cmdlines work with datalines */
  }

  /* set ADDR */
  if (op->addr.nbytes) {
    if (op->addr.nbytes == 4) {
      FSPICmd.b.addrbits = FSPI_ADDR_32BITS;
    } else if (op->addr.nbytes == 3) {
      FSPICmd.b.addrbits = FSPI_ADDR_24BITS;
    } else {
      FSPICmd.b.addrbits = FSPI_ADDR_XBITS;
      pReg->ABIT0        = op->addr.nbytes * 8 - 1;
    }

    FSPICtrl.b.addrlines = op->addr.buswidth == 4 ? FSPI_LINES_X4 : FSPI_LINES_X1;
  }

  /* set DUMMY*/
  if (op->dummy.nbytes) {
    switch (op->dummy.buswidth) {
      case 4:
        FSPICmd.b.dummybits = op->dummy.nbytes * 2;
        break;
      case 2:
        FSPICmd.b.dummybits = op->dummy.nbytes * 4;
        break;
      default:
        FSPICmd.b.dummybits = op->dummy.nbytes * 8;
        break;
    }
  }

  /* set DATA */
  WRITE_REG (pReg->LEN_EXT, op->data.nbytes);
  if (op->data.nbytes) {
    if (op->data.dir == HAL_SPI_MEM_DATA_OUT) {
      FSPICmd.b.rw = FSPI_WRITE;
    }

    if (op->data.buswidth == 4) {
      FSPICtrl.b.datalines = FSPI_LINES_X4;
    } else if (op->data.buswidth == 2) {
      FSPICtrl.b.datalines = FSPI_LINES_X2;
    } else {
      FSPICtrl.b.datalines = FSPI_LINES_X1;
    }
  }

  /* spitial setting */
  FSPICtrl.b.sps = host->mode & HAL_SPI_CPHA;
  FSPICmd.b.cs   = host->cs;
  if ((op->data.nbytes == 0) && op->addr.nbytes) {
    FSPICmd.b.rw = FSPI_WRITE;
  }

  if (!(pReg->FSR & FSPI_FSR_TXES_EMPTY) || !(pReg->FSR & FSPI_FSR_RXES_EMPTY) || (pReg->SR & FSPI_SR_SR_BUSY)) {
    FSPI_Reset (host);
  }

  // FSPI_DBG("%s 1 %x %x %x\n", __func__, op->addr.nbytes, op->dummy.nbytes, op->data.nbytes);
  // FSPI_DBG("%s 2 %lx %lx %lx %x\n", __func__, FSPICtrl.d32, FSPICmd.d32, op->addr.val, host->cs);

  /* config FSPI */
  switch (host->cs) {
    case 0:
      pReg->CTRL0 = FSPICtrl.d32;
      break;
    case 1:
      pReg->CTRL1 = FSPICtrl.d32;
      break;
    default:
      break;
  }

  pReg->CMD = FSPICmd.d32;
  if (op->addr.nbytes) {
    pReg->ADDR = op->addr.val;
  }

  return RETURN_SUCCESS;
}

/**
 * @brief  IO transfer.
 * @param  host: FSPI host.
 * @param  len: data n bytes.
 * @param  data: transfer buffer.
 * @param  dir: transfer direction.
 * @return RETURN_STATUS.
 */
RETURN_STATUS
HAL_FSPI_XferData (
  struct HAL_FSPI_HOST  *host,
  UINT32                len,
  void                  *data,
  UINT32                dir
  )
{
  RETURN_STATUS      ret = RETURN_SUCCESS;
  __IO FSPIFSR_DATA  fifostat;
  INT32              timeout = 0;
  UINT32             i, words;
  UINT32             *pData = (UINT32 *)data;
  struct FSPI_REG    *pReg  = host->instance;
  UINT32             temp   = 0;

  HAL_ASSERT (data && len);

  if (len && (len < 4) && (dir == FSPI_WRITE)) {
    if (len == 1) {
      temp = *((uint8_t *)data);
    } else if (len == 2) {
      temp = *((uint16_t *)data);
    } else {
      temp = ((uint8_t *)data)[0] | ((uint8_t *)data)[1] << 8 | ((uint8_t *)data)[2] << 16;
    }

    pData = &temp;
  } else if ((len >= 4) && !HAL_IS_ALIGNED ((long)data, 4)) {
    FSPI_DBG ("%s data unaligned access\n", __func__);
  }

  /* FSPI_DBG("%s %p len %lx word0 %lx dir %lx\n", __func__, pData, len, pData[0], dir); */
  if (dir == FSPI_WRITE) {
    words = (len + 3) >> 2;
    while (words) {
      fifostat.d32 = pReg->FSR;
      if (fifostat.b.txlevel > 0) {
        UINT32  count = MIN (words, fifostat.b.txlevel);

        for (i = 0; i < count; i++) {
          pReg->DATA = *pData++;
          words--;
        }

        if (words == 0) {
          break;
        }

        timeout = 0;
      } else {
        HAL_CPUDelayUs (1);
        if (timeout++ > 10000) {
          ret = RETURN_TIMEOUT;
          break;
        }
      }
    }
  } else {
    UINT32  bytes = len & 0x3;

    words = len >> 2;
    while (words) {
      fifostat.d32 = pReg->FSR;
      if (fifostat.b.rxlevel > 0) {
        UINT32  count = MIN (words, fifostat.b.rxlevel);
        if (count == 15) {
          /* Reduce CPU cost */
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;

          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          *pData++ = pReg->DATA;
          words   -= 15;
        } else {
          for (i = 0; i < count; i++) {
            *pData++ = pReg->DATA;
          }

          words -= count;
        }

        if (0 == words) {
          break;
        }

        timeout = 0;
      } else {
        HAL_CPUDelayUs (1);
        if (timeout++ > 10000) {
          ret = RETURN_TIMEOUT;
          break;
        }
      }
    }

    timeout = 0;
    while (bytes) {
      fifostat.d32 = pReg->FSR;
      if (fifostat.b.rxlevel > 0) {
        uint8_t  *pData1 = (uint8_t *)pData;
        words = pReg->DATA;
        for (i = 0; i < bytes; i++) {
          pData1[i] = (uint8_t)((words >> (i * 8)) & 0xFF);
        }

        break;
      } else {
        HAL_CPUDelayUs (1);
        if (timeout++ > 10000) {
          ret = RETURN_TIMEOUT;
          break;
        }
      }
    }
  }

  return ret;
}

/**
 * @brief  Wait for FSPI host transfer finished.
 * @return RETURN_STATUS.
 */
RETURN_STATUS
HAL_FSPI_XferDone (
  struct HAL_FSPI_HOST  *host
  )
{
  RETURN_STATUS    ret     = RETURN_SUCCESS;
  INT32            timeout = 0;
  struct FSPI_REG  *pReg   = host->instance;

  while (pReg->SR & FSPI_SR_SR_BUSY) {
    HAL_CPUDelayUs (1);
    if (timeout++ > 100000) {
      /*wait 100ms*/
      ret = RETURN_TIMEOUT;
      break;
    }
  }

  HAL_CPUDelayUs (1);  // CS# High Time (read/write) >100ns

  return ret;
}

/**
 * @brief  SPI Nor flash data transmission interface to support open source specifications SNOR.
 * @param  host: FSPI host.
 * @param  op: flash operation protocol.
 * @return RETURN_STATUS.
 * @attention Set host->cs to select chip.
 */
RETURN_STATUS
HAL_FSPI_SpiXfer (
  struct HAL_FSPI_HOST   *host,
  struct HAL_SPI_MEM_OP  *op
  )
{
  RETURN_STATUS  ret    = RETURN_SUCCESS;
  UINT32         dir    = op->data.dir;
  void           *pData = NULL;

  HAL_ASSERT (IS_FSPI_INSTANCE (host->instance));

  if (op->data.buf.in) {
    pData = (void *)op->data.buf.in;
  } else if (op->data.buf.out) {
    pData = (void *)op->data.buf.out;
  }

  HAL_FSPI_XferStart (host, op);
  if (pData) {
    ret = HAL_FSPI_XferData (host, op->data.nbytes, pData, dir);
    if (ret) {
      FSPI_DBG ("%s xfer data failed ret %d\n", __func__, ret);

      return ret;
    }
  }

  return HAL_FSPI_XferDone (host);
}

/** @} */

/** @defgroup FSPI_Exported_Functions_Group4 Init and DeInit Functions

 This section provides functions allowing to init and deinit the module:

 *  @{
 */

/**
 * @brief  Init FSPI.
 * @param  host: FSPI host.
 * @return RETURN_STATUS.
 */
RETURN_STATUS
HAL_FSPI_Init (
  struct HAL_FSPI_HOST  *host
  )
{
  INT32            timeout = 0;
  struct FSPI_REG  *pReg;
  RETURN_STATUS    ret = RETURN_SUCCESS;

  HAL_ASSERT (IS_FSPI_INSTANCE (host->instance));

  pReg       = host->instance;
  pReg->MODE = 0;
  while (pReg->SR & FSPI_SR_SR_BUSY) {
    HAL_CPUDelayUs (1);
    if (timeout++ > 1000) {
      return RETURN_TIMEOUT;
    }
  }

  FSPI_ContModeInit (host);
  pReg->CTRL0 = 0;

 #if (FSPI_VER > FSPI_VER_VER_3)
  WRITE_REG (pReg->LEN_CTRL, FSPI_LEN_CTRL_TRB_SEL_MASK);
 #endif

  return ret;
}

/**
 * @brief  DeInit FSPI.
 * @param  host: FSPI host.
 * @return RETURN_STATUS.
 */
RETURN_STATUS
HAL_FSPI_DeInit (
  struct HAL_FSPI_HOST  *host
  )
{
  HAL_ASSERT (IS_FSPI_INSTANCE (host->instance));

  host->instance->MODE = 0;
  FSPI_ContModeDeInit (host);
  FSPI_Reset (host);

  return RETURN_SUCCESS;
}

/**
 * @brief  Set FSPI delay line.
 * @param  host: FSPI host.
 * @param  cells: delay line cells.
 * @return RETURN_STATUS.
 */
RETURN_STATUS
HAL_FSPI_SetDelayLines (
  struct HAL_FSPI_HOST  *host,
  uint8_t               cells
  )
{
  HAL_ASSERT (IS_FSPI_INSTANCE (host->instance));
  if (host->cs == 0) {
    WRITE_REG (host->instance->DLL_CTRL0, 1 << FSPI_DLL_CTRL0_SCLK_SMP_SEL_SHIFT | cells);
  } else {
    WRITE_REG (host->instance->DLL_CTRL1, 1 << FSPI_DLL_CTRL0_SCLK_SMP_SEL_SHIFT | cells);
  }

  return RETURN_SUCCESS;
}

UINT32
HAL_FSPI_GetMaxIoSize (
  struct HAL_FSPI_HOST  *host
  )
{
  HAL_ASSERT (IS_FSPI_INSTANCE (host->instance));

  return FSPI_MAX_IOSIZE_VER4;
}
